<?php

class EmailAddress
{
  public $email;
  public $name;

  public function __construct($email, $name = NULL)
  {
    $this->email = $this->validateEmail($email);
    $this->name = $name;
  }

  public function toString()
  {
    if (!is_null($this->name)) {
      return "\"$this->name\" <$this->email>";
    } else {
      return  $this->email;
    }
  }

  protected function validateEmail($email)
  {
    $emailExpression = "^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+" .
        "(\.[a-z0-9-]+)*(\.[a-z]{2,3})$";
    if (!eregi($emailExpression, $email)) {
      throw new InvalidEmailExpression($email);
    }
    return $email;
  }
}

class EmailAddressCollection
{
  protected $emailAddresses = array();

  public function addEmailAddress(EmailAddress $email)
  {
    $this->emailAddresses[] = $email;
  }

  public function toString()
  {
    $emailAddresses = array();
    foreach ($this->emailAddresses as $email) {
      $emailAddresses[] = $email->toString();
    }
    return implode(', ', $emailAddresses);
  }
}

class EmailSender
{
  protected $recipients;

  protected $from;
  protected $replyTo;

  protected $ccs;
  protected $bccs;

  protected $subject;
  protected $body;

  protected $headers = array();

  public function __construct() {
    $this->recipients = new EmailAddressCollection();
    $this->ccs = new EmailAddressCollection();
    $this->bccs = new EmailAddressCollection();
  }

  public function setFrom($email, $name = NULL)
  {
    $this->from = new EmailAddress($email, $name);
  }

  public function setReplyTo($email, $name = NULL)
  {
    $this->replyTo = new EmailAddress($email, $name);
  }

  public function setSubject($subject)
  {
    $this->subject = $subject;
  }

  public function setBody($body)
  {
    $this->body = $body;
  }

  public function addRecipient($email, $name = NULL)
  {
    $this->recipients->addEmailAddress(new EmailAddress($email, $name));
  }

  public function addCc($email, $name = NULL)
  {
    $this->ccs->addEmailAddress(new EmailAddress($email, $name));
  }

  public function addBcc($email, $name = NULL)
  {
    $this->bccs->addEmailAddress(new EmailAddress($email, $name));
  }

  public function addHeader($name, $value)
  {
    $this->headers[] = array($name, $value);
  }

  public function send($test = false)
  {
    $this->validate();

    $headerString = $this->getHeaderString();
    $recipientString = $this->recipients->toString();

    if ($test) {
      echo "$recipientString\n";
      echo "$this->subject\n";
      echo "$this->body\n";
      echo "$headerString\n";
      exit();
    }

    $success = mail($recipientString, $this->subject, $this->body,
        $headerString);

    if (!$success) {
      throw new Exception('The mail() function failed');
    }
  }

  protected function getHeaderString()
  {
    $headers = array();
    $headers[] = $this->formatHeader('From', $this->from->toString());
    if (isset($this->replyTo)) {
      $headers[] = $this->formatHeader('Reply-To', $this->replyTo->toString());
    }
    $headers[] = $this->formatHeader('Cc', $this->ccs->toString());
    $headers[] = $this->formatHeader('Bcc', $this->bccs->toString());
    foreach ($this->headers as $header) {
      list($name, $value) = $header;
      $headers[] = $this->formatHeader($name, $value);
    }
    $headers[] = $this->formatHeader('X-Mailer', 'PHP/' . phpversion());
    return (implode('', $headers));
  }

  protected function formatHeader($name, $value)
  {
    return "$name: $value\r\n";
  }

  protected function validate() {
    if (count($this->recipients) == 0) {
      throw new LogicException('Recipient not specified');
    }
    if (!isset($this->from)) {
      throw new LogicException('Sender not specified');
    }
    if (!isset($this->subject)) {
      throw new LogicException('Subject not specified');
    }
    if (!isset($this->body)) {
      throw new LogicException('Body not specified');
    }
  }
}

class InvalidEmailException extends Exception
{
  public function __construct($email)
  {
    $this->message = "The specified email is invalid: '$email'";
  }
}
?>